package com.zmy.rxcache.rxjava2;

import android.util.Log;

import androidx.annotation.Nullable;

import com.zmy.rxcache.Reply;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import retrofit2.CallAdapter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.Result;

public class RxJavaReplyCallFactory extends CallAdapter.Factory {


    private static final String TAG = "RxJavaReplyCallFactory";
    private final @Nullable
    Scheduler scheduler;
    private final boolean isAsync;

    private RxJavaReplyCallFactory(@Nullable Scheduler scheduler, boolean isAsync) {
        this.scheduler = scheduler;
        this.isAsync = isAsync;
    }

    public static RxJavaReplyCallFactory create() {
        return new RxJavaReplyCallFactory(null, false);
    }

    /**
     * Returns an instance which creates asynchronous observables. Applying
     * {@link Observable#subscribeOn} has no effect on stream types created by this factory.
     */
    public static RxJavaReplyCallFactory createAsync() {
        return new RxJavaReplyCallFactory(null, true);
    }

    /**
     * Returns an instance which creates synchronous observables that
     * {@linkplain Observable#subscribeOn(Scheduler) subscribe on} {@code scheduler} by default.
     */
    @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
    public static RxJavaReplyCallFactory createWithScheduler(Scheduler scheduler) {
        if (scheduler == null) throw new NullPointerException("scheduler == null");
        return new RxJavaReplyCallFactory(scheduler, false);
    }

    public static CallAdapter<?, ?> createRxJavaCallAdapter(Type responseType,
                                                            @Nullable Scheduler scheduler,
                                                            boolean isAsync,
                                                            boolean isResult,
                                                            boolean isBody,
                                                            boolean isFlowable,
                                                            boolean isSingle,
                                                            boolean isMaybe,
                                                            boolean isCompletable) {
        try {
            Class<?> callAdapterClass = Class.forName("retrofit2.adapter.rxjava2.RxJava2CallAdapter");
            Constructor<?>[] constructors = callAdapterClass.getConstructors();
            Constructor constructor = null;
            for (int i = 0; i < constructors.length; i++) {
                if (constructors[i].getParameterTypes().length > 0) {
                    constructor = constructors[i];
                    break;
                }

            }

            return (CallAdapter<?, ?>) constructor.newInstance(responseType,
                    scheduler,
                    isAsync,
                    isResult,
                    isBody,
                    isFlowable,
                    isSingle,
                    isMaybe,
                    isCompletable);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException("cannot init callAdapter");
    }

    @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

        Log.i(TAG, "get: ---1");
        Class<?> rawType = getRawType(returnType);

        if (rawType == Completable.class) {
            // Completable is not parameterized (which is what the rest of this method deals with) so it
            // can only be created with a single configuration.

            return createRxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, false,
                    false, true);

        }

        boolean isFlowable = rawType == Flowable.class;
        boolean isSingle = rawType == Single.class;
        boolean isMaybe = rawType == Maybe.class;
        if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
            return null;
        }

        boolean isResult = false;
        boolean isBody = false;
        Type responseType = null;
        if (!(returnType instanceof ParameterizedType)) {
            String name = isFlowable ? "Flowable"
                    : isSingle ? "Single"
                    : isMaybe ? "Maybe" : "Observable";
            throw new IllegalStateException(name + " return type must be parameterized"
                    + " as " + name + "<Foo> or " + name + "<? extends Foo>");
        }

        Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
        Class<?> rawObservableType = getRawType(observableType);
        if (rawObservableType == Reply.class) {

            Class<?> rawReplyType = getRawType(rawObservableType);

            if (rawReplyType == Response.class) {
                if (!(observableType instanceof ParameterizedType)) {
                    throw new IllegalStateException("Response must be parameterized"
                            + " as Response<Foo> or Response<? extends Foo>");
                }
                responseType = getParameterUpperBound(0, (ParameterizedType) observableType);

            } else if (rawReplyType == Result.class) {
                if (!(observableType instanceof ParameterizedType)) {
                    throw new IllegalStateException("Result must be parameterized"
                            + " as Result<Foo> or Result<? extends Foo>");
                }
                responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
                isResult = true;
            } else {
                responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
                isBody = true;
            }

            return new RxJava2ReplyCallAdapter(responseType, scheduler, isAsync,isBody, isFlowable,
                    isSingle, isMaybe, false);

        } else return null;


    }
}
